-- Socket
-- Create by weism
-- Socket的连接、读写操作

module("Socket", package.seeall);

require("socket.core");

-- socket
local client = nil
local mIsConnected = false;
local mIp;
local mPort;
local mConnectTime = 0;
local mIsConnecting = false;

-- 待发送的数据
local toSendData = {};

-- 检测是否处于连接中的调度
local schedulerId = nil;

-- 内部函数声明
-- local checkConnected;

-- 当前是不是出于连接中
function isConnected()
    return client ~= nil and mIsConnected;
end

-- 是否正在连接
function isConnecting()
    return mIsConnecting;
end

-- 连接到服务器
function connect(ip, port)
    -- 已经处于连接中了
    if isConnected() then
        EventMgr.fire(event.NETWORK_CONNECTED);
        return
    end

    mIsConnecting = true;

    -- 创建TCP连接
    trace("Socket", "开始连接服务器（%s:%d）", ip, port);
    mConnectTime = 0;
    toSendData = {};
    mIp = ip;
    mPort = port;

    local ipv6Only = false;

    if isIosPlatform() then
        local addrinfo, err = socket.dns.getaddrinfo(ip);

        if err ~= nil then
            release_print(err);
        end

        if addrinfo ~= nil then
            for i,v in ipairs(addrinfo) do
                if v.family == "inet6" then
                    ipv6Only = true;
                    break;
                end
            end
        end
    end

    if ipv6Only then
        trace("Socket", "Socket family is ipv6");
        client = socket.tcp6()
    else
        trace("Socket", "Socket family is ipv4");
        client = socket.tcp();
    end

    client:settimeout(0.00001);

    -- 确保连接上
    if not isVerifyClient() and not schedulerId then
        schedulerId = cc.Director:getInstance():getScheduler():scheduleScriptFunc(checkConnected, 0.2, false);
    end
end

-- 断开连接
function disconnect()
    if not isConnected() then return end

    if client ~= nil then
        client:shutdown("both");
        client:close();
        client = nil;
        mIsConnected = false;
    end

    if schedulerId ~=nil then
        -- 停止监测了
        cc.Director:getInstance():getScheduler():unscheduleScriptEntry(schedulerId);
        schedulerId = nil;
    end

    -- 需要先停止定时器，因为其他地方关注了此事件做重连
    EventMgr.fire(event.NETWORK_DISCONNECTED);
end

-- 读取数据
function recv()
    if not isConnected() then return nil end

    -- 读取数据
    -- local readable, _, err = socket.select({ client }, nil, 0);
    -- if readable and #readable == 1 then
        local line, err = client:receive("*a");
        if err and err ~= "timeout" then
            print("recv:" .. err);
            -- 有错误，连接断开了
            disconnect();
        elseif not err then
            if not USE_CPP_PACKER then
                local data = Buffer.create(0);
                Buffer.setstr(data, 1, #line, line);
                Communicate.read(data);
            else
                Communicate.read(line);
            end
        end
    -- end
end

-- 发送数据
function send(buffer)
    if not isConnected() then return false end

    -- 插入到队列中
    table.insert(toSendData, { buffer, 0 });
    return true;
end

-- 定时处理
function update()
    if not isConnected() then return end

    -- 发送数据
    local maxTimes = 10;
    while maxTimes >= 0 do
        maxTimes = maxTimes - 1;
        if  #toSendData > 0 then
            -- 测试socket状态，如果可以发送再发送数据
            --local writeable, err = socket.skip(1, socket.select(nil, { client }, 0));
            --if table.getn(writeable) == 1 then
                -- 这条消息以及当前发送到哪个字节了(一条消息可能需要发送多次)
                local data = toSendData[1][1];
                local offset = toSendData[1][2];

                local result, err, send, dataLen;
                if not USE_CPP_PACKER then
                    dataLen = Buffer.size(data);
                    result, err, send = client:send(Buffer.byte(data), offset + 1);
                else
                    dataLen = string.len(data);
                    result, err, send = client:send(data, offset + 1);
                end

                if SHOW_NETWORK_TIP == 1 then
                    showHint("send result: " .. tostring(result) .. " err: " .. tostring(err));
                end

                if result == nil then
                    -- 发送失败了
                    trace("Socket", "发送失败，err:" .. err);

                    if err == "closed" then
                        disconnect();
                        return;
                    end
                    offset = send;
                else
                    offset = result;
                end

                if offset >= dataLen then
                    -- 全部发送出去了
                    table.remove(toSendData, 1);
                else
                    toSendData[1][2] = offset;
                end
            --end
        else
            break;
        end
    end

    -- 取得数据
    Profiler.funcBegin("Socket:recv");
    recv();
    Profiler.funcEnd("Socket:recv");
end

-- 检查是不是连接上了
function checkConnected()
    if client == nil then
        mIsConnecting = false;
        return false;
    end

    local succ, status = client:connect(mIp, mPort, "*", 0);
    if succ == 1 or status == "already connected" then
        mIsConnecting = false;
        if not isConnected() then
            -- 后续的操作都为非阻塞了
            client:settimeout(0);

            -- 刚开始连接上，需要发布事件
            mIsConnected = true;
            EventMgr.fire(event.NETWORK_CONNECTED);
        end

        if schedulerId ~= nil then
            -- 停止监测了
            cc.Director:getInstance():getScheduler():unscheduleScriptEntry(schedulerId);
            schedulerId = nil;
        end

        return true;
    end

    mConnectTime = mConnectTime + 1;

    if (mConnectTime > 60) then
        -- 认为超时了
        trace("Socket", "连接服务器超时了，succ = %s，status = %s", tostring(succ), status or "不应该");
        mIsConnecting = false;

        if schedulerId ~= nil then
            -- 停止监测
            cc.Director:getInstance():getScheduler():unscheduleScriptEntry(schedulerId);
            schedulerId = nil;
        end

        disconnect();

        EventMgr.fire(event.NETWORK_CONNECT_TIMEOUT);
        mConnectTime = 0
    end

    return false;
end
